package com.px.user.service;

import com.px.common.core.RespCode;
import com.px.common.core.RespException;
import com.px.common.util.JsonUtil;
import com.px.user.entity.Reward;
import com.px.user.entity.RewardHis;
import com.px.user.entity.Vote;
import com.px.user.mapper.RewardMapper;
import com.px.user.mapper.RoomMapper;
import com.px.user.mapper.UserMapper;
import com.px.user.mapper.VoteMapper;
import com.px.user.model.RewardRank;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.*;

@Slf4j
@Service
public class RoomService {

    @Autowired
    private RoomMapper roomMapper;

    @Autowired
    private VoteMapper voteMapper;

    @Autowired
    private RewardMapper rewardMapper;

    @Autowired
    private UserService userService;

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    /**
     * 打赏排名redis key配置
     */
    @Value("${px.reward-rank.rw-rank}")
    private String rwRank;
    @Value("${px.reward-rank.rw-rank-pre}")
    private String rwRankPre;
    @Value("${px.reward-rank.rw-nickname}")
    private String rwNickname;

    @Transactional
    public void updateVote(Vote vote) {
        voteMapper.insert(vote);
        if (vote.getUpdown() == -1)
            roomMapper.voteDown(vote.getRoomId());
        else if (vote.getUpdown() == 1)
            roomMapper.voteUp(vote.getRoomId());
        else
            roomMapper.voteFlat(vote.getRoomId());
        log.info(vote.getUserId() + " in roomId: " + vote.getRoomId() + " vote: " + vote.getUpdown());
    }

    public Vote voteGet(Long roomId) {
        return roomMapper.findUpDown(roomId);
    }

    private static final String CHAT_USERS = "room_user_list_";

    public List<Map> chatUser(Long roomId, Integer limit) {
        List<Map> resp = new ArrayList<>();
        List<String> users = redisTemplate.<String, String>opsForHash().values(CHAT_USERS + roomId);
        int count = 0;
        for (String user : users) {
            if (count >= limit)
                break;
            resp.add(JsonUtil.fromJson(user, Map.class));
            count++;
        }
        return resp;
    }

    /**
     * 礼物打赏
     *
     * @param rewardHis
     */
    @Transactional
    public void userReward(RewardHis rewardHis) {
        Reward reward = rewardMapper.findById(rewardHis.getRewardId());
        if (reward == null || reward.getMoney().compareTo(BigDecimal.valueOf(0)) < 0) {
            assert reward != null;
            log.error("admin set reward money error with id: " + reward.getId());
            throw new RespException(RespCode.PARA_ERROR, null);
        }
        rewardHis.setMoney(reward.getMoney());
        if (!userService.isMoneyGte(rewardHis.getFromUserId(), rewardHis.getMoney()))
            throw new RespException(RespCode.NOT_ENOUGH_MONEY, null);

        userService.subMoney(rewardHis.getFromUserId(), rewardHis.getMoney());
        rewardMapper.insertRewardHis(rewardHis);
        userService.addMoney(rewardHis.getToUserId(), rewardHis.getMoney());
        userRewardRank(rewardHis);
    }

    public void userRewardRank(RewardHis rewardHis) {
        Long roomId = rewardHis.getRoomId();
        BigDecimal money = rewardHis.getMoney();
        String userIdStr = String.valueOf(rewardHis.getFromUserId());
        Double preMoney = redisTemplate.opsForZSet().score(rwRank + roomId, userIdStr);

        preRewardRank(roomId);

        if (preMoney == null)
            redisTemplate.opsForZSet().add(rwRank + roomId, userIdStr, money.doubleValue());
        else
            redisTemplate.opsForZSet().add(rwRank + roomId, userIdStr, money.doubleValue() + preMoney);

        redisTemplate.opsForHash().put(rwNickname, userIdStr, userMapper.findNicknameById(rewardHis.getFromUserId()));
    }

    public void preRewardRank(long roomId) {
        Set<ZSetOperations.TypedTuple<String>> typedTuples =
                redisTemplate.opsForZSet().reverseRangeWithScores(rwRank + roomId, 0, -1);
        if (typedTuples == null)
            return;
        Iterator<ZSetOperations.TypedTuple<String>> rankTuple = typedTuples.iterator();
        int rank = 0;
        while (rankTuple.hasNext()) {
            ZSetOperations.TypedTuple<String> tuple = rankTuple.next();
            if (tuple.getValue() == null)
                continue;
            rank += 1;
            redisTemplate.opsForHash().put(rwRankPre + roomId, tuple.getValue(), String.valueOf(rank));
        }
    }

    /**
     * 打赏排名
     *
     * @param roomId
     * @param start
     * @param end
     * @return
     */
    public List<RewardRank> getUserRewardRank(long roomId, int start, int end) {
        Set<ZSetOperations.TypedTuple<String>> typedTuples =
                redisTemplate.opsForZSet().reverseRangeWithScores(rwRank + roomId, start, end);
        if (typedTuples == null)
            return null;

        List<RewardRank> rewardRanks = new ArrayList<>();
        Iterator<ZSetOperations.TypedTuple<String>> rankTuple = typedTuples.iterator();
        int rank = 0;
        while (rankTuple.hasNext()) {
            ZSetOperations.TypedTuple<String> tuple = rankTuple.next();
            if (tuple.getValue() == null || tuple.getScore() == null)
                continue;
            rank += 1;
            int preRank = rank;
            String preRankStr = redisTemplate.<String, String>opsForHash().get(rwRankPre + roomId, tuple.getValue());
            if (preRankStr != null)
                preRank = Integer.parseInt(preRankStr);

            rewardRanks.add(
                    RewardRank.builder()
                            .userId(Long.valueOf(tuple.getValue()))
                            .nickname(redisTemplate.<String, String>opsForHash().get(rwNickname, tuple.getValue()))
                            .money(BigDecimal.valueOf(tuple.getScore()))
                            .rank(rank)
                            .upDown(preRank - rank)
                            .build());
        }
        return rewardRanks;
    }
}
